#include <th/drivers/nand_chip.h>
#include <th/lib/nand.h>
#include <config.h>
#include <th/lib/stdio.h>

void nand_read_id(uint8_t *id, uint8_t *len)
{
	while (nand_chip_dev_ready(NULL) == false);
	nand_chip_cmd_ctrl(NULL, NAND_CMD_READID, NAND_CTRL_CLE | NAND_CTRL_CHANGE);
	nand_chip_cmd_ctrl(NULL, 0x00, NAND_CTRL_ALE | NAND_CTRL_CHANGE);
	*len = 2;
	nand_chip_read_buf(NULL, id, *len);
	nand_chip_cmd_ctrl(NULL, NAND_CMD_NONE, NAND_CTRL_CHANGE);
}

void nand_command(uint32_t cmd, int32_t column, int32_t page_addr)
{
	while (nand_chip_dev_ready(NULL) == false);
	nand_chip_cmd_ctrl(NULL, cmd, NAND_CTRL_CLE | NAND_CTRL_CHANGE);
	if (column != -1) {
		nand_chip_cmd_ctrl(NULL, column, NAND_CTRL_ALE | NAND_CTRL_CHANGE);
	}
	if (page_addr != -1) {
		nand_chip_cmd_ctrl(NULL, page_addr, NAND_CTRL_ALE | NAND_CTRL_CHANGE);
		nand_chip_cmd_ctrl(NULL, page_addr >> 8, NAND_CTRL_ALE);
		nand_chip_cmd_ctrl(NULL, page_addr >> 16, NAND_CTRL_ALE);
	}
	if ((cmd == NAND_CMD_ERASE1) || (cmd == NAND_CMD_SEQIN)) {
		nand_chip_cmd_ctrl(NULL, NAND_CMD_NONE, NAND_CTRL_CHANGE);
	}
	while (nand_chip_dev_ready(NULL) == false);
	if ((cmd == NAND_CMD_ERASE1) || (cmd == NAND_CMD_SEQIN)) {
		nand_chip_cmd_ctrl(NULL, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
	}
}

uint32_t nand_is_bad_block(uint32_t block)
{
	uint8_t tmp;
	
	nand_command(NAND_CMD_READOOB, CONFIG_SYS_NAND_BAD_BLOCK_POS, block * CONFIG_SYS_NAND_PAGE_COUNT);
	nand_chip_read_buf(NULL, &tmp, 1);
	nand_chip_cmd_ctrl(NULL, NAND_CMD_NONE, NAND_CTRL_CHANGE);
	if (tmp != 0xff) {
		return false;
	}

	return true;
}

void nand_read_page(uint32_t block, uint32_t page, uint8_t *dst)
{
	nand_command(NAND_CMD_READ0, 0, page + block * CONFIG_SYS_NAND_PAGE_COUNT);
	nand_chip_read_buf(NULL, dst, CONFIG_SYS_NAND_PAGE_SIZE);
	nand_chip_cmd_ctrl(NULL, NAND_CMD_NONE, NAND_CTRL_CHANGE);
}

uint32_t nand_read(uint32_t offset, uint32_t length, uint8_t *buffer)
{
	uint32_t block, lastblock;
	uint32_t page;
	
	if ((length & CONFIG_SYS_NAND_PAGE_SIZE) || (offset & CONFIG_SYS_NAND_PAGE_SIZE)) {
		return false;
	}

	block = offset / CONFIG_SYS_NAND_BLOCK_SIZE;
	page = (offset % CONFIG_SYS_NAND_BLOCK_SIZE) / CONFIG_SYS_NAND_PAGE_SIZE;
	lastblock = (offset + length - 1) / CONFIG_SYS_NAND_BLOCK_SIZE;

	while (block <= lastblock) {
		if (nand_is_bad_block(block) == false) {
			while ((length != 0) && (page < CONFIG_SYS_NAND_PAGE_COUNT)) {
				nand_read_page(block, page, buffer);
				buffer += CONFIG_SYS_NAND_PAGE_SIZE;
				page++;
				length -= CONFIG_SYS_NAND_PAGE_SIZE;
			}
			page = 0;
		} else {
			lastblock++;
		}
		block++;
	}	

	return true;
}


void nand_init(void)
{
	nand_chip_init();
}

